#ifndef NETWORKSERVICES_QTHREADPOOL_H
#define NETWORKSERVICES_QTHREADPOOL_H

#include <time.h>
#include <list>
#include <queue>
#include <mutex>
#include <future>
#include <atomic>
#include <memory>
#include <chrono>
#include <thread>
#include <utility>
#include <functional>
#include <condition_variable>

#define DEFAULT_THREAD_POOL_MIN_THREAD_NUM  1
#define DEFAULT_THREAD_POOL_MAX_THREAD_NUM  std::thread::hardware_concurrency()
#define DEFAULT_THREAD_POOL_MAX_IDLE_TIME   60000 // ms

class QThreadPool {
public:
    using Task = std::function<void()>;

    QThreadPool(int minThreads = DEFAULT_THREAD_POOL_MIN_THREAD_NUM,
                int maxThreadNum = DEFAULT_THREAD_POOL_MAX_THREAD_NUM,
                int maxIdleMs = DEFAULT_THREAD_POOL_MAX_IDLE_TIME)
            : minThreadNum(minThreads), maxThreadNum(maxThreadNum), maxIdleTime(maxIdleMs), status(STOP),
              curThreadNum(0), idleThreadNum(0)
    {}

    virtual ~QThreadPool()
    {
        Stop();
    }

    int CurrentThreadNum()
    {
        return curThreadNum;
    }

    int IdleThreadNum()
    {
        return idleThreadNum;
    }

    size_t TaskNum()
    {
        std::lock_guard<std::mutex> locker(taskMutex);
        return tasks.size();
    }

    bool IsStarted()
    {
        return status != STOP;
    }

    bool IsStopped()
    {
        return status == STOP;
    }

    void Start(int startThreads = 0)
    {
        if (status != STOP) {
            return;
        }
        status = RUNNING;
        if (startThreads < minThreadNum) {
            startThreads = minThreadNum;
        }
        if (startThreads > maxThreadNum) {
            startThreads = maxThreadNum;
        }
        for (int i = 0; i < startThreads; ++i) {
            CreateThread();
        }
    }

    void Stop()
    {
        if (status == STOP) {
            return;
        }
        status = STOP;
        taskCond.notify_all();
        for (auto &i: threads) {
            if (i.thread->joinable()) {
                i.thread->join();
            }
        }
        threads.clear();
        curThreadNum = 0;
        idleThreadNum = 0;
    }

    void Pause()
    {
        if (status == RUNNING) {
            status = PAUSE;
        }
    }

    void Resume()
    {
        if (status == PAUSE) {
            status = RUNNING;
        }
    }

    void Wait()
    {
        while (status != STOP) {
            if (tasks.empty() && idleThreadNum == curThreadNum) {
                break;
            }
            std::this_thread::yield();
        }
    }

    template<class Fn, class... Args>
    auto Commit(Fn &&fn, Args &&... args) -> std::future<decltype(fn(args...))>
    {
        if (status == STOP) {
            Start();
        }
        if (idleThreadNum <= tasks.size() && curThreadNum < maxThreadNum) {
            CreateThread();
        }
        using RetType = decltype(fn(args...));
        auto task = std::make_shared<std::packaged_task<RetType()> >(
                std::bind(std::forward<Fn>(fn), std::forward<Args>(args)...));
        std::future<RetType> future = task->get_future();
        {
            std::lock_guard<std::mutex> locker(taskMutex);
            tasks.emplace([task] { (*task)(); });
        }

        taskCond.notify_one();
        return future;
    }

protected:
    bool CreateThread()
    {
        if (curThreadNum >= maxThreadNum) {
            return false;
        }
        std::thread *thread = new std::thread([this] {
            while (status != STOP) {
                while (status == PAUSE) {
                    std::this_thread::yield();
                }
                Task task;
                {
                    std::unique_lock<std::mutex> locker(taskMutex);
                    taskCond.wait_for(locker, std::chrono::milliseconds(maxIdleTime), [this]() {
                        return status == STOP || !tasks.empty();
                    });
                    if (status == STOP) {
                        return;
                    }
                    if (tasks.empty()) {
                        if (curThreadNum > minThreadNum) {
                            DelThread(std::this_thread::get_id());
                            return;
                        }
                        continue;
                    }
                    --idleThreadNum;
                    task = std::move(tasks.front());
                    tasks.pop();
                }
                if (task) {
                    task();
                    ++idleThreadNum;
                }
            }
        });
        AddThread(thread);
        return true;
    }

    void AddThread(std::thread *thread)
    {
        threadMutex.lock();
        ++curThreadNum;
        ++idleThreadNum;
        ThreadData data;
        data.thread = std::shared_ptr<std::thread>(thread);
        data.id = thread->get_id();
        data.status = RUNNING;
        data.startTime = time(NULL);
        data.stopTime = 0;
        threads.emplace_back(data);
        threadMutex.unlock();
    }

    void DelThread(std::thread::id id)
    {
        time_t now = time(NULL);
        threadMutex.lock();
        --curThreadNum;
        --idleThreadNum;
        auto iter = threads.begin();
        while (iter != threads.end()) {
            if (iter->status == STOP && now > iter->stopTime) {
                if (iter->thread->joinable()) {
                    iter->thread->join();
                    iter = threads.erase(iter);
                    continue;
                }
            } else if (iter->id == id) {
                iter->status = STOP;
                iter->stopTime = time(NULL);
            }
            ++iter;
        }
        threadMutex.unlock();
    }

public:
    int minThreadNum;
    int maxThreadNum;
    int maxIdleTime;

protected:
    enum Status {
        STOP,
        RUNNING,
        PAUSE,
    };
    struct ThreadData {
        std::shared_ptr<std::thread> thread;
        std::thread::id id;
        Status status;
        time_t startTime;
        time_t stopTime;
    };
    std::atomic<Status> status;
    std::atomic<int> curThreadNum;
    std::atomic<int> idleThreadNum;
    std::list<ThreadData> threads;
    std::mutex threadMutex;
    std::queue<Task> tasks;
    std::mutex taskMutex;
    std::condition_variable taskCond;
};

#endif // NETWORKSERVICES_QTHREADPOOL_H
